﻿/*
 * BitmapBlock
 * 
 * initial implementation : 3.12.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;

namespace Petra.Gis.ImagingServices.Tiff
{

  public abstract partial class BitmapBlock : BitmapSource
  {



    #region BitmapSource CopyPixels



    /// <summary>
    /// copy entire bitmap area to destination
    /// </summary>
    /// <param name="pixels"></param>
    /// <param name="stride"></param>
    /// <param name="offset"></param>
    public sealed override void CopyPixels(Array pixels, int stride, int offset)
    {
      Int32Rect sourceRect = new Int32Rect(0, 0, this.PixelWidth, this.PixelHeight);
      CopyPixels(sourceRect, pixels, stride, offset);
    }






    public sealed override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset)
    {
      int elementSize;
      int bufferSize;
      Type elementType;
      ValidateArrayAndGetInfo(pixels, out elementSize, out bufferSize, out elementType);

      if (offset < 0)
      {
        throw new ArgumentOutOfRangeException("offset");
      }

      // We accept arrays of arbitrary value types - but not reference types.
      if (elementType == null || !elementType.IsValueType)
      {
        throw new ArgumentException("pixels");
      }

      checked
      {
        int offsetInBytes = offset * elementSize;
        if (offsetInBytes >= bufferSize)
        {
          throw new IndexOutOfRangeException();
        }

        // Get the address of the data in the array by pinning it.
        GCHandle arrayHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
        try
        {
          // Adjust the buffer and bufferSize to account for the offset.
          IntPtr buffer = arrayHandle.AddrOfPinnedObject();
          buffer = new IntPtr(((long)buffer) + (long)offsetInBytes);
          bufferSize -= offsetInBytes;

          CopyPixels(sourceRect, buffer, bufferSize, stride);
        }
        finally
        {
          arrayHandle.Free();
        }
      }
    }




    /// <summary>
    /// this method will automatically adjust size of input rectangle
    /// to overlap size by using Bounds
    /// </summary>
    /// <param name="sourceRect"></param>
    /// <param name="buffer"></param>
    /// <param name="bufferSize"></param>
    /// <param name="stride"></param>
    public sealed override void CopyPixels(Int32Rect sourceRect, IntPtr buffer, int bufferSize, int stride)
    {
      // WIC would specify NULL for the source rect to indicate that the
      // entire content should be copied.  WPF turns that into an empty
      // rect, which we inflate here to be the entire bounds.
      if (sourceRect.IsEmpty)
      {
        sourceRect.X = 0;
        sourceRect.Y = 0;
        sourceRect.Width = PixelWidth;
        sourceRect.Height = PixelHeight;
      }

      if (sourceRect.X < 0 || sourceRect.Y < 0 || sourceRect.Width < 1 || sourceRect.Height < 1)
        throw new ArgumentOutOfRangeException("sourceRect");

      // if source rect doesnt overlap this block
      // do nothing
      if(!(
        sourceRect.X < (_bounds.X + _bounds.Width) &&
        sourceRect.Y < (_bounds.Y + _bounds.Height) &&
        (sourceRect.X + sourceRect.Width) > _bounds.X &&
        (sourceRect.Y + sourceRect.Height) > _bounds.Y))
        return;

      // ok overlap exists
      // compute overlap area
      Int32Rect outRect = getBoundsIntersection(sourceRect);

      if (buffer == IntPtr.Zero)
      {
        throw new ArgumentNullException("invalid buffer");
      }

      checked
      {
        if (stride < 1)
        {
          throw new ArgumentOutOfRangeException("stride");
        }

        uint minStrideInBits = (uint)(outRect.Width * Format.BitsPerPixel);
        uint minStrideInBytes = ((minStrideInBits + 7) / 8);
        if (stride < minStrideInBytes)
        {
          throw new ArgumentOutOfRangeException("stride");
        }

        if (bufferSize < 1)
        {
          throw new ArgumentOutOfRangeException("bufferSize");
        }

        uint minBufferSize = (uint)((outRect.Height - 1) * stride) + minStrideInBytes;
        if (bufferSize < minBufferSize)
        {
          throw new ArgumentOutOfRangeException("bufferSize");
        }
      }

      CopyPixelsCore(outRect, stride, bufferSize, buffer);
    }






    /// <summary>
    /// copies data from BitmapSource to output buffer
    /// </summary>
    /// <param name="sourceRect">subselect from source by rectangle</param>
    /// <param name="stride">target stride</param>
    /// <param name="bufferSize">size of target buffer</param>
    /// <param name="buffer">buffer</param>
    protected abstract void CopyPixelsCore(Int32Rect sourceRect, int stride, int bufferSize, IntPtr buffer);






    /// <summary>
    ///     Get the size of the specified array and of the elements in it.
    /// </summary>
    /// <param name="pixels">
    ///     The array to get info about.
    /// </param>
    /// <param name="elementSize">
    ///     On output, will contain the size of the elements in the array.
    /// </param>
    /// <param name="sourceBufferSize">
    ///     On output, will contain the size of the array.
    /// </param>
    /// <param name="elementType">
    ///     On output, will contain the type of the elements in the array.
    /// </param>
    private void ValidateArrayAndGetInfo(Array pixels,
                                         out int elementSize,
                                         out int sourceBufferSize,
                                         out Type elementType)
    {
      //
      // Assure that a valid pixels Array was provided.
      //
      if (pixels == null)
      {
        throw new ArgumentNullException("pixels");
      }

      if (pixels.Rank == 1)
      {
        if (pixels.GetLength(0) <= 0)
        {
          throw new ArgumentException("pixels");
        }
        else
        {
          checked
          {
            object exemplar = pixels.GetValue(0);
            elementSize = Marshal.SizeOf(exemplar);
            sourceBufferSize = pixels.GetLength(0) * elementSize;
            elementType = exemplar.GetType();
          }
        }

      }
      else if (pixels.Rank == 2)
      {
        if (pixels.GetLength(0) <= 0 || pixels.GetLength(1) <= 0)
        {
          throw new ArgumentException("pixels");
        }
        else
        {
          checked
          {
            object exemplar = pixels.GetValue(0, 0);
            elementSize = Marshal.SizeOf(exemplar);
            sourceBufferSize = pixels.GetLength(0) * pixels.GetLength(1) * elementSize;
            elementType = exemplar.GetType();
          }
        }
      }
      else
      {
        throw new ArgumentException("pixels");
      }
    }





    protected Int32Rect getBoundsIntersection(Int32Rect srcRect)
    {
      Int32Rect outRect = new Int32Rect();

      outRect.X = (srcRect.X < _bounds.X) ? 0 : srcRect.X - _bounds.X;
      outRect.Y = (srcRect.Y < _bounds.Y) ? 0 : srcRect.Y - _bounds.Y;

      outRect.Width = ((srcRect.X + srcRect.Width) > (_bounds.X + _bounds.Width))
        ? (_bounds.Width - outRect.X)
        : (srcRect.X + srcRect.Width - _bounds.X) - outRect.X;

      outRect.Height = ((srcRect.Y + srcRect.Height) > (_bounds.Y + _bounds.Height))
        ? (_bounds.Height - outRect.Y)
        : (srcRect.Y + srcRect.Height - _bounds.Y) - outRect.Y;

      return outRect;
    }


    #endregion BitmapSource CopyPixels


  }
}
